package com.shuiyun.jtt.endpoint;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.crypto.asymmetric.RSA;
import com.shuiyun.jtt.anno.Endpoint;
import com.shuiyun.jtt.anno.Mapping;
import com.shuiyun.jtt.enums.JT808;
import com.shuiyun.jtt.enums.PushMessageThreadPool;
import com.shuiyun.jtt.message.Header;
import com.shuiyun.jtt.message.JTMessage;
import com.shuiyun.jtt.message.jt808.*;
import com.shuiyun.jtt.message.jt808.extend.IntegerId;
import com.shuiyun.jtt.message.jt808.extend.MultimediaItem;
import com.shuiyun.jtt.session.Session;
import com.shuiyun.jtt.utils.DeviceCache;
import com.shuiyun.jtt.utils.IdUtil;
import com.shuiyun.jtt.utils.TachographUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.io.*;
import java.math.BigInteger;
import java.util.*;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

/**
 * @author shuiyun
 * @description 808协议处理器
 * @date 2022-04-20 00:04
 */
@Component
@Slf4j
@Endpoint
public class JT808Endpoint {

    private final static String FILE_PATH = "data/";

    private final AtomicInteger serialNo = new AtomicInteger(0);

    @Mapping(types = JT808.终端注册应答0x8100, desc = "终端注册")
    public T0001 t8100(T8100 message, Header header) {
        log.info("---->收到终端注册响应信息：{}", message);
        return defaultResponse(header);
    }

    @Mapping(types = {JT808.平台通用应答0x8001, JT808.平台通用应答0x8001}, desc = "平台通用应答")
    public void t8001(T0001 message, Header header) {
        log.info("---->收到通用应答：{}", header);
    }

    @Mapping(types = JT808.查询服务器时间应答0x8004, desc = "查询服务器时间应答")
    public T0001 t8004(T8004 message, Header header) {
        log.info("服务器时间：{}", message.getServerTime());
        return defaultResponse(header);
    }

    @Mapping(types = JT808.设置终端参数0x8103, desc = "设置终端参数")
    public T0001 t8103(T8103 message, Header header, Session session) {
        log.info("{} 设置终端参数：{}", session, message.getTerminalParam());
        message.getTerminalParam().forEach((k, v) -> {
            DeviceCache.instance.getTerminalParamMap().put(k, v);
        });
        return defaultResponse(header);
    }

    @Mapping(types = JT808.查询终端参数0x8104, desc = "查询终端参数")
    public T0104 t8104(Header header) {
        T0104 t0104 = new T0104(header.getSerialNo(), DeviceCache.instance.getTerminalParamMap());
        t0104.setHeader(JT808.查询终端参数应答0x0104, header.getMobileNo(), serialNo.getAndIncrement());
        return t0104;
    }

    @Mapping(types = JT808.查询指定终端参数0x8106, desc = "查询指定终端参数")
    public T0104 t8106(T8106 message, Header header) {
        List<Integer> ids = IdUtil.getId(message.getIds());
        Map<Integer, Object> resultMap = new HashMap<>(ids.size());
        ids.forEach(id -> {
            Object o = DeviceCache.instance.getTerminalParamMap().get(id);
            if (ObjectUtil.isNotNull(o)) {
                resultMap.put(id, o);
            }
        });
        T0104 t0104 = new T0104(header.getSerialNo(), resultMap);
        t0104.setHeader(JT808.查询终端参数应答0x0104, header.getMobileNo(), serialNo.getAndIncrement());
        return t0104;
    }

    @Mapping(types = JT808.查询终端属性0x8107, desc = "查询终端属性")
    public T0107 t8107(JTMessage message, Header header, Session session) {
        T0107 t0107 = new T0107(15, "1010", "T-131", "715", "19112345678", "1.0", "1.1", 15, 63);
        t0107.setHeader(JT808.查询终端属性应答0x0107, header.getMobileNo(), serialNo.getAndIncrement());
        return t0107;
    }

    @Mapping(types = JT808.下发终端升级包0x8108, desc = "下发终端升级包")
    public T0001 t8108(T8108 message, Header header, Session session) {
        File file = new File(FILE_PATH + message.getVersion());
        try (FileOutputStream outputStream = new FileOutputStream(file);) {
            ByteArrayInputStream inputStream = new ByteArrayInputStream(message.getPacket());
            IoUtil.copy(inputStream, outputStream);
        } catch (Exception e) {
            log.error("拷贝文件流失败");
        }
        // 过5秒之后 上传一个升级成功指令
        PushMessageThreadPool.instance.getPushMessageThreadPool().execute(() -> {
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            T0108 t0108 = new T0108(message.getType(), 0);
            t0108.setHeader(JT808.终端升级结果通知0x0108, header.getMobileNo(), serialNo.getAndIncrement());
            session.getChannel().writeAndFlush(t0108);

        });
        return defaultResponse(header);
    }

    @Mapping(types = JT808.位置信息查询0x8201, desc = "位置查询")
    public T0201And0500 t8201(Header header, Session session) {
        T0200 location = new T0200(0, 3, 30554380, 104064048, 420, 80, 10, new Date(), null);
        T0201And0500 t0201And0500 = new T0201And0500(header.getSerialNo(), location);
        t0201And0500.setHeader(JT808.位置信息查询应答0x0201, header.getMobileNo(), session.nextSerialNo());
        return t0201And0500;
    }

    @Mapping(types = JT808.临时位置跟踪控制0x8202, desc = "临时位置跟踪控制")
    public T0001 t8202(T8202 message, Header header, Session session) {
        log.info("收到{}临时位置跟踪控制信息,开始定时推送定位信息：{}", session, message);

        int count = (message.getValidityPeriod() / message.getInterval());
        ThreadPoolExecutor executor = PushMessageThreadPool.instance.getPushMessageThreadPool();
        executor.execute(() -> {
            for (int i = 0; i < count; i++) {
                T0200 location = new T0200(0, 3, 30554380, 104064048, 420, 80, 10, new Date(), null);
                location.setHeader(JT808.位置信息汇报0x0200, header.getMobileNo(), session.nextSerialNo());
                session.getChannel().writeAndFlush(location);
                try {
                    Thread.sleep(1000 * message.getInterval());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        return defaultResponse(header);
    }

    @Mapping(types = JT808.人工确认报警消息0x8203, desc = "人工确认消息类型")
    public T0001 t8203(T8203 message, Header header) {
        log.info("收到人工确认报警消息：{}", message);
        return defaultResponse(header);
    }

    @Mapping(types = JT808.服务器向终端发起链路检测请求0x8204, desc = "人工确认消息类型")
    public T0001 t8203(Session session, Header header) {
        log.info("收到链路检测请求消息：{}", session);
        return defaultResponse(header);
    }

    @Mapping(types = JT808.文本信息下发0x8300, desc = "文本信息下发")
    public T0001 t8300(T8300 message, Header header) {
        log.info("收到文本下发消息：{}", message);
        return defaultResponse(header);
    }

    @Mapping(types = JT808.电话回拨0x8400, desc = "电话回拨")
    public T0001 t8400(T8400 message, Header header) {
        log.info("收到电话回拨消息：{}", message);
        return defaultResponse(header);
    }

    @Mapping(types = JT808.设置电话本0x8401, desc = "设置电话本")
    public T0001 t8401(T8401 message, Header header) {
        switch (message.getType()) {
            case 0:
                // 删除所有电话本联系人
                DeviceCache.instance.getContactsMap().clear();
                break;
            case 1:
                DeviceCache.instance.getContactsMap().clear();
                message.getList().forEach(p -> {
                    DeviceCache.instance.getContactsMap().put(p.getContacts(), p);
                });
                break;
            case 2:
            case 3:
                message.getList().forEach(p -> {
                    DeviceCache.instance.getContactsMap().put(p.getContacts(), p);
                });
                break;
            default:
                log.error("接收到前段发送的设置电话本信息，类型错误：" + message.getType());
                break;
        }

        log.info("现有电话本信息：{}", DeviceCache.instance.getContactsMap().values());
        return defaultResponse(header);
    }

    @Mapping(types = JT808.车辆控制0x8500, desc = "车辆控制")
    public T0201And0500 t8500(T8500 message, Header header, Session session) {
        log.info("收到车辆控制消息：{}", message);
        T0200 location = new T0200(0, 3, 30554380, 104064048, 420, 80, 10, new Date(), null);
        T0201And0500 t0201And0500 = new T0201And0500(header.getSerialNo(), location);
        t0201And0500.setHeader(JT808.车辆控制应答0x0500, header.getMobileNo(), session.nextSerialNo());
        return t0201And0500;
    }

    @Mapping(types = JT808.设置圆形区域0x8600, desc = "设置圆形区域")
    public T0001 t8600(T8600 message, Header header) {

        // 如果是更新操作 则需要额外清除缓存一次
        if (message.getOperation() == 0) {
            DeviceCache.instance.getCircularAreaMap().clear();
        }
        message.getAreas().forEach(area -> {
            DeviceCache.instance.getCircularAreaMap().put(area.getAreaId(), area);
        });
        log.info("现有圆形区域：{}", DeviceCache.instance.getCircularAreaMap());
        return defaultResponse(header);
    }

    @Mapping(types = JT808.设置矩形区域0x8602, desc = "设置矩形区域")
    public T0001 t8602(T8602 message, Header header) {
        // 如果是更新操作 则需要额外清除缓存一次
        if (message.getOperation() == 0) {
            DeviceCache.instance.getCircularAreaMap().clear();
        }
        message.getAreas().forEach(area -> {
            DeviceCache.instance.getRectangleAreaMap().put(area.getAreaId(), area);
        });
        log.info("现有矩形区域：{}", DeviceCache.instance.getRectangleAreaMap());
        return defaultResponse(header);
    }

    @Mapping(types = JT808.设置多边形区域0x8604, desc = "设置多边形区域")
    public T0001 t8604(T8604 message, Header header) {
        DeviceCache.instance.getPolygonAreaMap().put(message.getAreaId(), message);
        log.info("现有多边形区域：{}", DeviceCache.instance.getPolygonAreaMap());
        return defaultResponse(header);
    }

    @Mapping(types = JT808.设置路线0x8606, desc = "设置路线")
    public T0001 t8606(T8606 message, Header header) {
        DeviceCache.instance.getRouteAreaMap().put(message.getAreaId(), message);
        log.info("现有路线区域：{}", DeviceCache.instance.getRouteAreaMap());
        return defaultResponse(header);
    }

    @Mapping(types = {JT808.删除圆形区域0x8601, JT808.删除矩形区域0x8603, JT808.删除多边形区域0x8605, JT808.删除路线0x8607}, desc = "删除区域")
    public T0001 t8601(T8601 message, Header header) {
        List<Integer> ids = IdUtil.getId(message.getIds());
        switch (header.getMessageId()) {
            case JT808.删除圆形区域0x8601:
                ids.forEach(id -> {
                    DeviceCache.instance.getCircularAreaMap().remove(id);
                });
                log.info("现有圆形区域：{}", DeviceCache.instance.getCircularAreaMap());
                break;
            case JT808.删除矩形区域0x8603:
                ids.forEach(id -> {
                    DeviceCache.instance.getRectangleAreaMap().remove(id);
                });
                log.info("现有矩形区域：{}", DeviceCache.instance.getRectangleAreaMap());
                break;
            case JT808.删除多边形区域0x8605:
                ids.forEach(id -> {
                    DeviceCache.instance.getPolygonAreaMap().remove(id);
                });
                log.info("现有多边形区域：{}", DeviceCache.instance.getPolygonAreaMap());
                break;
            case JT808.删除路线0x8607:
                ids.forEach(id -> {
                    DeviceCache.instance.getRouteAreaMap().remove(id);
                });
                log.info("现有路线区域：{}", DeviceCache.instance.getRouteAreaMap());
                break;
            default:
                break;
        }
        return defaultResponse(header);
    }

    @Mapping(types = JT808.查询区域或线路数据0x8608, desc = "查询区域或路线数据")
    public T0608 t8608(T8608 message, Session session, Header header) {

        List<Integer> ids = IdUtil.getId(message.getIds());
        T0608 t0608 = new T0608();
        t0608.setHeader(new Header(JT808.查询区域或线路数据应答0x0608, serialNo.getAndIncrement(), header.getMobileNo()));
        t0608.setType(message.getType());
        switch (message.getType()) {
            case 1:
                List<T8600> circularArea = null;
                if (CollectionUtil.isEmpty(message.getIds())) {
                    circularArea = DeviceCache.instance.getCircularAreaMap().values().stream()
                            .map(p -> new T8600(0, Collections.singletonList(p))).collect(Collectors.toList());
                } else {
                    circularArea = DeviceCache.instance.getCircularAreaMap().values().stream().filter(p -> ids.contains(p.getAreaId()))
                            .map(p -> new T8600(0, Collections.singletonList(p))).collect(Collectors.toList());
                }
                t0608.setCircularArea(circularArea);
                break;
            case 2:
                List<T8602> rectangleArea = null;
                if (CollectionUtil.isEmpty(message.getIds())) {
                    rectangleArea = DeviceCache.instance.getRectangleAreaMap().values().stream().map(p -> new T8602(0, Collections.singletonList(p)))
                            .collect(Collectors.toList());
                } else {
                    rectangleArea = DeviceCache.instance.getRectangleAreaMap().values().stream().filter(p -> ids.contains(p.getAreaId()))
                            .map(p -> new T8602(0, Collections.singletonList(p))).collect(Collectors.toList());
                }
                t0608.setRectangleArea(rectangleArea);
                break;
            case 3:
                List<T8604> polygonArea = null;
                if (CollectionUtil.isEmpty(message.getIds())) {
                    polygonArea = new ArrayList<>(DeviceCache.instance.getPolygonAreaMap().values());
                } else {
                    polygonArea = DeviceCache.instance.getPolygonAreaMap().values().stream().filter(p -> ids.contains(p.getAreaId())).collect(Collectors.toList());
                }
                t0608.setPolygonArea(polygonArea);
                break;
            case 4:
                List<T8606> routeArea = null;
                if (CollectionUtil.isEmpty(message.getIds())) {
                    routeArea = new ArrayList<>(DeviceCache.instance.getRouteAreaMap().values());
                } else {
                    routeArea = DeviceCache.instance.getRouteAreaMap().values().stream().filter(p -> ids.contains(p.getAreaId())).collect(Collectors.toList());
                }
                t0608.setRouteArea(routeArea);
                break;
            default:
                break;
        }

        return t0608;
    }


    @Mapping(types = JT808.行驶记录仪数据采集命令0x8700, desc = "行驶记录仪数据采集命令")
    public T0700 t8700(T8700 message, Header header, Session session) {
        byte[] result = TachographUtil.handle(message);
        T0700 t0700 = new T0700(header.getSerialNo(),message.getCommand(),result);
        t0700.setHeader(JT808.行驶记录数据上传0x0700,header.getMobileNo(),serialNo.getAndIncrement());
        return t0700;
    }


    @Mapping(types = JT808.行驶记录仪参数下传命令0x8701, desc = "行驶记录仪参数下传命令")
    public T0001 t8701(T8701 message, Header header) {
        TachographUtil.handle(message);
        return defaultResponse(header);
    }


    @Mapping(types = JT808.上报驾驶员身份信息请求0x8702, desc = "上报驾驶员身份信息请求")
    public T0702 t8702(JTMessage message, Header header) {
        T0702 t0702 = new T0702(0x01, new Date(), 0x00, "水云", "510723200012121234",
                "成都市交管局", "203012", "510723200012121234");
        t0702.setHeader(JT808.驾驶员身份信息采集上报0x0702, header.getMobileNo(), serialNo.getAndIncrement());
        return t0702;
    }

    @Mapping(types = JT808.多媒体数据上传应答0x8800, desc = "多媒体数据上传应答0x8800")
    public T0001 t8800(T8800 message, Session session, Header header) {
        log.info("{} 发送的多媒体事件信息上传信息：{}", session, message);
        return defaultResponse(header);
    }

    @Mapping(types = JT808.摄像头立即拍摄命令0x8801, desc = "摄像头立即拍摄命令0x8801")
    public T0805 t8801(T8801 message, Session session, Header header) {
        log.info("{} 发送的摄像头立即拍摄命令：{}", session, message);
        T0805 t0805 = new T0805(header.getSerialNo(), 0, Collections.singletonList(new IntegerId(1)));
        t0805.setHeader(JT808.摄像头立即拍摄命令应答0x0805, header.getMobileNo(), serialNo.getAndIncrement());
        // 如果需要立即上传 则启动异步线程上传
        if (message.getSaveTag() == 0) {
            PushMessageThreadPool.instance.getPushMessageThreadPool().execute(() -> {
                // 睡一百毫秒之后再执行 避免推送错误
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                T0801 t0801 = new T0801(1, 0, 0, 1, message.getChannelId());
                t0801.setLocation(new T0200(0, 3, 30554380, 104064048, 420, 80, 10, new Date(), null));
                t0801.setBytes(getFileBytes());
                t0801.setHeader(JT808.多媒体数据上传0x0801, header.getMobileNo(), serialNo.getAndIncrement());
                session.getChannel().writeAndFlush(t0801);
            });
        }
        return t0805;
    }

    @Mapping(types = JT808.存储多媒体数据检索0x8802, desc = "存储多媒体数据检索0x8802")
    public T0802 t8802(T8802 message, Session session, Header header) {
        log.info("{} 存储多媒体数据检索：{}", session, message);
        T0802 t0802 = new T0802();
        t0802.setSerialNo(header.getSerialNo());
        MultimediaItem item1 = new MultimediaItem(1, message.getType(), message.getChannelId(), message.getEvent(), new T0200(0, 3, 30554380, 104064048, 420, 80, 10, new Date(), null));
        MultimediaItem item2 = new MultimediaItem(2, message.getType(), message.getChannelId(), message.getEvent(), new T0200(0, 3, 30554380, 104064048, 420, 80, 10, new Date(), null));
        MultimediaItem item3 = new MultimediaItem(3, message.getType(), message.getChannelId(), message.getEvent(), new T0200(0, 3, 30554380, 104064048, 420, 80, 10, new Date(), null));
        t0802.setItems(Arrays.asList(item1, item2, item3));
        t0802.setHeader(JT808.存储多媒体数据检索应答0x0802, header.getMobileNo(), serialNo.getAndIncrement());
        return t0802;
    }

    @Mapping(types = JT808.存储多媒体数据上传0x8803, desc = "存储多媒体数据上传")
    public T0001 t8803(T8803 message, Session session, Header header) {
        log.info("{} 存储多媒体数据检索：{}", session, message);
        return defaultResponse(header);
    }

    @Mapping(types = JT808.录音开始命令0x8804, desc = "录音开始命令")
    public T0001 t8804(T8804 message, Session session, Header header) {
        log.info("{} 录音开始命令：{}", session, message);
        // 如果要求立即上传音频 则上传音频信息
        if (message.getSaveTag() == 0) {
            PushMessageThreadPool.instance.getPushMessageThreadPool().execute(() -> {
                // 睡一百毫秒之后再执行 避免推送错误
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                T0801 t0801 = new T0801(1, 1, 2, 1, 0);
                t0801.setLocation(new T0200(0, 3, 30554380, 104064048, 420, 80, 10, new Date(), null));
                t0801.setBytes(getFileBytes("static/demo.mp3"));
                t0801.setHeader(JT808.多媒体数据上传0x0801, header.getMobileNo(), serialNo.getAndIncrement());
                session.getChannel().writeAndFlush(t0801);
            });
        }
        return defaultResponse(header);
    }


    @Mapping(types = JT808.单条存储多媒体数据检索上传命令0x8805, desc = "单条存储多媒体数据检索上传命令")
    public T0001 t8805(T8805 message, Session session, Header header) {
        log.info("{} 单条存储多媒体数据检索上传命令：{}", session, message);
        return defaultResponse(header);
    }


    @Mapping(types = JT808.数据下行透传0x8900, desc = "数据下行透传")
    public T0001 t0900(T0900 message, Header header, Session session) {
        log.info("{} 数据下行透传：{}", session, message);
        return defaultResponse(header);
    }

    @Mapping(types = JT808.平台RSA公钥0x8A00, desc = "平台RSA公钥")
    public T0001 t0a00(T0A00 message, Header header, Session session) {
        log.info("{} 平台RSA公钥：{}", session, RSA.generatePublicKey(new BigInteger(message.getN()), new BigInteger(String.valueOf(message.getE()))));
        return defaultResponse(header);
    }


    /////////////////////// 以下是私有方法 ////////////////////////////

    /**
     * 获取测试文件流 固定文件
     *
     * @return 固定文件流
     * @author shuiyun
     * @date 2022/5/8 22:54
     */
    private byte[] getFileBytes() {
        String fileName = "static/alarm00.jpg";
        return getFileBytes(fileName);
    }

    /**
     * 通用应答
     *
     * @param header 请求消息的头信息
     * @return 通用应答
     * @author shuiyun
     * @date 2022/5/9 21:32
     */
    private T0001 defaultResponse(Header header) {
        T0001 t0001 = new T0001(header.getSerialNo(), header.getMessageId(), 0);
        t0001.setHeader(JT808.终端通用应答0x0001, header.getMobileNo(), serialNo.getAndIncrement());
        return t0001;
    }

    /**
     * 根据文件相对路径获取文件流
     *
     * @param fileName 文件名
     * @return 文件流
     * @author shuiyun
     * @date 2022/5/8 22:53
     */
    private byte[] getFileBytes(String fileName) {
        try (InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(fileName);
             ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        ) {
            if (null == inputStream) {
                throw new RuntimeException("原始文件流获取失败");
            }
            IoUtil.copy(inputStream, outputStream);
            return outputStream.toByteArray();
        } catch (Exception e) {
            log.error("获取文件流失败");
            return new byte[0];
        }

    }

}
